#include <assert.h>
#include <stdlib.h>

#include "ll.h"
#include "debug.h"

#define DEBUG 0

// Private function declarations
ll_node* ll_create_node();

struct ll_node {
	void *data;
	struct ll_node *prev;
	struct ll_node *next;
};

struct ll_list {
	struct ll_node *first;
	struct ll_node *last;
	unsigned int size;
};

// Get element at index i
ll_list* ll_create() {
	ll_list* new = (ll_list*)malloc(sizeof(ll_list));
	new->size = 0;
	return new;
}

void ll_delete(ll_list* list) {
    assert(list != NULL);

    while (ll_pop(list)) ;
	free(list);
}

// Append an element to an list
void ll_push(ll_list* list, void *data) {
	assert(list != NULL);

    ll_node* new_last = ll_create_node();
	new_last->data = data;

	if (list->size == 0) {
		list->first = list->last = new_last;
	} else {
		ll_node* last = list->last;
		last->next = new_last;
		new_last->prev = last;
		list->last = new_last;
	}

	list->size++;
}

// Remove and return the last element of a list
void* ll_pop(ll_list* list) {
    assert(list != NULL);

    debug_print("popping from list, size: %d\n", list->size);

	if (list->size == 0) {
		return NULL;
	}

	ll_node* last = list->last;
	assert(last != NULL);

	if (list->size == 1) {
		list->first = list->last = NULL;
	} else {
		ll_node* next_to_last = last->prev;
		assert(next_to_last != NULL);

		next_to_last->next = NULL;
		list->last = next_to_last;
	}

	list->size--;
	void *ret = last->data;
	free(last);

	return ret;
}

// Get element at index i
void* ll_get(ll_list* list, unsigned int i) {
    assert(list != NULL);

    if (i >= list->size) {
		return NULL;
	}

	ll_node* cur = list->first;
	while (i--) cur = cur->next;

	return cur->data;
}

// Get first element
void* ll_first(ll_list* list) {
    assert(list != NULL);

    return list->first->data;
}

// Get last element
void* ll_last(ll_list* list) {
    assert(list != NULL);
	return list->last->data;
}

// Private functions
ll_node* ll_create_node() {
	return (ll_node*)malloc(sizeof(ll_node));
}

unsigned int ll_size(ll_list* list) {
    assert(list != NULL);
	return list->size;
}
